/*
 * topological-hex, a program to compute combinatorial hexahedral meshes.
 *
 * Copyright (C) <2018> <Université catholique de Louvain (UCL), Belgique>
 *
 * List of the contributors to the development, description and complete
 * License: see LICENSE file.
 *
 * This program (topological-hex) is free software:
 * you can redistribute it and/or modify it under the terms
 * of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program (see COPYING file).  If not,
 * see <http://www.gnu.org/licenses/>.
 */

#ifndef PROTOCOL_H_
#define PROTOCOL_H_

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#include "types.h"
#include "solver.h"

/**
 * @defgroup distributed-protocol Distributed Protocol
 *
 * Utilities used for communications between the client and the server in order
 * to explore a subproblem in a distributed fashion.
 *
 * The protocol used to communicate uses commands. A command is made up of a
 * 4-bit tag (@ref message_tag) indicating the type of the command and a 60-bit
 * argument, which may be ignored for some of the commands.
 *
 * All clients start by sending a username to the server (@ref write_name). This
 * is used only for logging purposes.
 *
 * There are two types of clients in the system:
 *
 *    1. The master client, which reads the input file and sends the initial set
 *       of subproblems to the server.
 *
 *       This client first sends a layout (@ref write_layout), followed by a set
 *       of subproblems and solution (@ref write_subproblem, @ref
 *       write_solution). It eventually sends a @ref TAG_EMPTY command to
 *       indicate that it is done. It then follows the same code path as other
 *       clients.
 *
 *    2. Other clients, which receive the memory layout from the server (@ref
 *       read_layout).
 *
 *       They request subproblems by sending a @ref TAG_REQUEST command to the
 *       server. They should then receive a sequence of @ref TAG_SUBPROBLEM
 *       problems.
 *
 *       Workers need to report progress to the server by sending @ref
 *       TAG_FINISHED commands after finishing the exploration of subproblems
 *       and @ref TAG_SOLUTION commands when finding solutions
 *
 * Eventually, the server will run out of subproblems to send to the
 * clients. Once a client receives 0 subproblems, it should stop sending
 * requests to the server. If the server somehow gets more subproblems
 * (e.g. because a client abnormally terminated), it sends a @ref TAG_WAKEUP
 * command to the clients, after which they are allowed to send more requests.
 *
 * All input/output functions take a predicate as a parameter. Once this
 * predicate returns false, the execution of the function is interrupted. This
 * can be used if a stopping criterion other than the connection being closed is
 * present:
 *
 * @code
 * bool should_stay_open(void *data) {
 *   return ((server*)data)->should_stay_open;
 * }
 *
 * // ...
 *
 * char buffer[256];
 * read_name(should_stop, &server, socket, buffer, sizeof(buffer));
 * @endcode
 */

/**
 * @addtogroup distributed-protocol
 * @{
 */

/**
 * String containing the port used by the server.
 */
#define SERVER_PORT "4395"

typedef enum message_tag {
  /**
   * Indicates that a subproblem will be sent. The argument of the command
   * should be the identifier of the subproblem.
   *
   * This is followed by a subproblem read using @ref read_subproblem.
   */
  TAG_SUBPROBLEM,

  /**
   * Currently unused.
   */
  TAG_SUBDIVIDE,

  /**
   * Indicates that a subproblem layout will follow. The layout is read using
   * @ref read_layout.
   */
  TAG_LAYOUT,

  /**
   * Indicates that the connection should be closed.
   */
  TAG_CLOSE,

  /**
   * Indicates that no more subproblems will be sent.
   *
   * This command is sent by the master client once it has sent its last
   * subproblem, and by the server after sending the last subproblem when
   * answering a request command.
   */
  TAG_EMPTY,

  /**
   * Indicates that a client has finished exploring a subproblem.
   *
   * The argument should match the identifier of the subproblem, which was the
   * argument of the @p TAG_SUBPROBLEM command.
   */
  TAG_FINISHED,

  /**
   * Indicates that a client is requesting that subproblems be sent to it.
   *
   * The argument is the maximum number of subproblems the server is
   * allowed to send.
   */
  TAG_REQUEST,

  /**
   * Indicates that a client has found a solution.
   *
   * This should be followed by the contents of the solution, read using @ref
   * read_solution.
   */
  TAG_SOLUTION,

  /**
   * Sent by the server to tell clients that they are allowed to send more
   * requests now that more subproblems have been made available.
   */
  TAG_WAKEUP,

  /**
   * Number of distinct tags.
   */
  TAG_COUNT /* Keep last */
} message_tag;

/**
 * Number of bits used by the tag. The rest of the 64-bit command is used to
 * store the argument.
 */
#define TAG_BITS 4

/**
 * Stores all the offsets needed to quickly serialize and deserialize
 * subproblems.
 */
typedef struct subproblem_layout  {
  uint32_t num_vertices;
  uint32_t num_quads;
  uint32_t num_hexes;
  uint32_t num_symmetries;
  uint32_t num_boundary_vertices;
  uint32_t max_no_goods_size;

  uint8_t flags;

  /**
   * Number of bytes used to send a subproblem.
   */
  size_t packet_size;

  size_t offset_hex_id;

  size_t offset_max_quad_id;
  size_t offset_max_vertex_id;
  size_t offset_quad_queue_size;
  size_t offset_quad_queue_offset;
  size_t offset_bound_mask;

  size_t offset_quads;
  size_t offset_hexes;
  size_t offset_quad_adjacency;

  size_t offset_quad_queue;
  size_t offset_num_occurrences;

  size_t offset_quad_descriptors;

  size_t offset_allowed_neighbors;
  size_t offset_known_neighbors;
  size_t offset_known_diagonals;
  size_t offset_known_quad_diagonals;
  size_t offset_domains;

  size_t offset_even_vertices;
  size_t offset_odd_vertices;

  size_t offset_boundary_group;
  size_t offset_nogoods_size;
  size_t offset_nogoods;

  size_t offset_symmetry_data;
} subproblem_layout;

/**
 * Makes a socket non-blocking.
 */
error_code configure_nonblock(int socket);

/**
 * Endianness-agnostic functions to write multi-byte integers into a stream of
 * bytes.
 *
 * This allows CPUs with multiple different endianness to communicate with one
 * another.
 */
void encode_uint64(void *buffer, uint64_t x);

/** @copydoc encode_uint64 */
uint64_t decode_uint64(void *buffer);

/** @copydoc encode_uint64 */
void encode_uint32(void *buffer, uint32_t x);

/** @copydoc encode_uint64 */
uint32_t decode_uint32(void *buffer);

/** @copydoc encode_uint64 */
void encode_uint16(void *buffer, uint16_t x);

/** @copydoc encode_uint64 */
uint16_t decode_uint16(void *buffer);

/**
 * Enables TCP keepalive for a socket. This is used to avoid disconnections when
 * the client is working on long-running subproblems and not sending any
 * messages to the server for long periods of time.
 *
 * Silently ignores failure.
 */
void configure_keepalive(int socket);

/**
 * Reads the name of the client into a buffer.
 *
 * @param [out] buffer C-string containing the name of the client
 * @param buffer_len Length of the buffer, including space needed to store the
 *   NUL terminator.
 */
bool read_name(bool (*cond)(void *data), void *data,
               int socket, char *buffer, size_t buffer_len);

/**
 * Sends the client name to the server.
 *
 * @param buffer The name of the user
 * @param len The length of the user-name
 */
bool write_name(bool (*cond)(void *data), void *data,
                int socket, const char *buffer, size_t len);

/**
 * Reads a command.
 *
 * @see message_tag
 *
 * @param [out] tag
 * @param [out] arg
 */
bool read_command(bool (*cond)(void *data), void *data,
                  int socket, message_tag *tag, uint64_t *arg);

/**
 * Sends a command.
 *
 * @see message_tag
 */
bool write_command(bool (*cond)(void *data), void *data,
                   int socket, message_tag tag, uint64_t arg);

/**
 * Reads a subproblem layout.
 *
 * @see write_layout
 *
 * @param [out] num_vertices Maximum number of vertices used in the solution
 * @param [out] num_quads Maximum number of quads used in the solution
 * @param [out] num_hexes Maximum number of hexahedra used in the solution
 */
bool read_layout(bool (*cond)(void *data), void *data,
                 int socket,
                 uint32_t *num_vertices, uint32_t *num_quads,
                 uint32_t *num_hexes, uint32_t *num_symmetries,
                 uint32_t *num_boundary_vertices,
                 uint32_t *max_no_goods_size, uint8_t *flags);

/**
 * Writes a subproblem layout.
 *
 * @see read_layout
 */
bool write_layout(bool (*cond)(void *data), void *data,
                  int socket,
                  uint32_t num_vertices, uint32_t num_quads,
                  uint32_t num_hexes, uint32_t num_symmetries,
                  uint32_t num_boundary_vertices,
                  uint32_t max_no_goods_size, uint8_t flags);

/**
 * Reads a solution into a buffer.
 *
 * @see write_solution
 *
 * @param [out] buffer Buffer large enough to store the solution (8*num_hexes +
 *                     1 bytes).
 * @param [out] vertices Pointer to the contents of the solution. This points
 *                       within @p buffer.
 * @param [out] num_hexes Number of hexahedra in the solution.
 * @param max_hex Maximum number of hexes that can be read in the buffer.
 */
bool read_solution(bool (*cond)(void *data), void *data,
                   int socket,
                   uint8_t *buffer,
                   vertex_index **vertices,
                   hex_index *num_hexes,
                   uint32_t max_hex);

/**
 * Sends a solution to the server.
 *
 * @see read_solution
 *
 * @param buffer Buffer large enough to store the solution (8*num_hexes + 1
 *   bytes)
 * @param vertices Pointer to the vertices of the solution
 * @param num_hexes Number of hexahedra in the solution
 */
bool write_solution(bool (*cond)(void *data), void *data,
                    int socket, uint8_t *buffer,
                    const vertex_index *vertices, hex_index num_hexes);

/**
 * Initializes a subproblem layout from the values read using @ref read_layout.
 */
size_t compute_subproblem_layout(
  subproblem_layout *layout,
  uint32_t num_vertices, uint32_t num_quads, uint32_t num_hexes,
  uint32_t num_symmetries, uint32_t num_boundary_vertices,
  uint32_t max_no_goods_size, uint8_t flags);

/**
 * Reads a subproblem into @p buffer without parsing it.
 *
 * @see write_raw_subproblem
 *
 * @param [out] buffer Buffer large enough to store a subproblem (@ref
 *   subproblem_layout::packet_size)
 * @param layout Offsets for the subproblem, as constructed using @ref
 *   compute_subproblem_layout.
 */
bool read_raw_subproblem(bool (*cond)(void *data), void *data,
                         int socket, uint8_t *buffer,
                         const subproblem_layout *layout);

/**
 * Writes an unparsed subproblem from @p buffer.
 *
 * @see read_raw_subproblem
 */
bool write_raw_subproblem(bool (*cond)(void *data), void *data,
                          int socket, const uint8_t *buffer,
                          const subproblem_layout *layout);

/**
 * Reads a subproblem.
 *
 * @see write_subproblem
 *
 * @param [out] buffer Buffer large enough to store a subproblem (@ref
 *   subproblem_layout::packet_size)
 * @param layout Offsets for the subproblem, as constructed using @ref
 *   compute_subproblem_layout.
 * @param [out] solver The read subproblem. Should have been allocated with a
 *   size large enough to handle any subproblem described by @p layout.
 */
bool read_subproblem(bool (*cond)(void *data), void *data,
                     int socket,
                     uint8_t *buffer,
                     const subproblem_layout *layout,
                     solver *solver);

/**
 * Reads a subproblem.
 *
 * @see read_subproblem
 */
bool write_subproblem(bool (*cond)(void *data), void *data,
                      int socket,
                      uint8_t *buffer,
                      const subproblem_layout *layout,
                      solver *solver);

/**
 * @}
 */

#endif
